{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Configurations for Colab"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import sys\n",
    "IN_COLAB = \"google.colab\" in sys.modules\n",
    "\n",
    "if IN_COLAB:\n",
    "    !apt install python-opengl\n",
    "    !apt install ffmpeg\n",
    "    !apt install xvfb\n",
    "    !pip install pyvirtualdisplay\n",
    "    from pyvirtualdisplay import Display\n",
    "    \n",
    "    # Start virtual display\n",
    "    dis = Display(visible=0, size=(400, 400))\n",
    "    dis.start()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 08. Rainbow\n",
    "\n",
    "[M. Hessel et al., \"Rainbow: Combining Improvements in Deep Reinforcement Learning.\" arXiv preprint arXiv:1710.02298, 2017.](https://arxiv.org/pdf/1710.02298.pdf)\n",
    "\n",
    "We will integrate all the following seven components into a single integrated agent, which is called Rainbow!\n",
    "\n",
    "1. DQN\n",
    "2. Double DQN\n",
    "3. Prioritized Experience Replay\n",
    "4. Dueling Network\n",
    "5. Noisy Network\n",
    "6. Categorical DQN\n",
    "7. N-step Learning\n",
    "\n",
    "This method shows an impressive performance on the Atari 2600 benchmark, both in terms of data efficiency and final performance. \n",
    "\n",
    "![rainbow](https://user-images.githubusercontent.com/14961526/60591412-61748100-9dd9-11e9-84fb-076c7a61fbab.png)\n",
    "\n",
    "However, the integration is not so simple because some of components are not independent each other, so we will look into a number of points that people especailly feel confused.\n",
    "\n",
    "1. Noisy Network <-> Dueling Network\n",
    "2. Dueling Network <-> Categorical DQN\n",
    "3. Categorical DQN <-> Double DQN"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "import math\n",
    "import os\n",
    "import random\n",
    "from collections import deque\n",
    "from typing import Deque, Dict, List, Tuple\n",
    "\n",
    "import gym\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "import torch.optim as optim\n",
    "from IPython.display import clear_output\n",
    "from torch.nn.utils import clip_grad_norm_\n",
    "\n",
    "# download segment tree module\n",
    "if IN_COLAB:\n",
    "    !wget https://raw.githubusercontent.com/curt-park/rainbow-is-all-you-need/master/segment_tree.py\n",
    "\n",
    "from segment_tree import MinSegmentTree, SumSegmentTree"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Replay buffer\n",
    "\n",
    "Same as the basic N-step buffer. \n",
    "\n",
    "(Please see *01.dqn.ipynb*, *07.n_step_learning.ipynb* for detailed description about the basic (n-step) replay buffer.)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "class ReplayBuffer:\n",
    "    \"\"\"A simple numpy replay buffer.\"\"\"\n",
    "\n",
    "    def __init__(\n",
    "        self, \n",
    "        obs_dim: int, \n",
    "        size: int, \n",
    "        batch_size: int = 32, \n",
    "        n_step: int = 1, \n",
    "        gamma: float = 0.99\n",
    "    ):\n",
    "        self.obs_buf = np.zeros([size, obs_dim], dtype=np.float32)\n",
    "        self.next_obs_buf = np.zeros([size, obs_dim], dtype=np.float32)\n",
    "        self.acts_buf = np.zeros([size], dtype=np.float32)\n",
    "        self.rews_buf = np.zeros([size], dtype=np.float32)\n",
    "        self.done_buf = np.zeros(size, dtype=np.float32)\n",
    "        self.max_size, self.batch_size = size, batch_size\n",
    "        self.ptr, self.size, = 0, 0\n",
    "        \n",
    "        # for N-step Learning\n",
    "        self.n_step_buffer = deque(maxlen=n_step)\n",
    "        self.n_step = n_step\n",
    "        self.gamma = gamma\n",
    "\n",
    "    def store(\n",
    "        self, \n",
    "        obs: np.ndarray, \n",
    "        act: np.ndarray, \n",
    "        rew: float, \n",
    "        next_obs: np.ndarray, \n",
    "        done: bool,\n",
    "    ) -> Tuple[np.ndarray, np.ndarray, float, np.ndarray, bool]:\n",
    "        transition = (obs, act, rew, next_obs, done)\n",
    "        self.n_step_buffer.append(transition)\n",
    "\n",
    "        # single step transition is not ready\n",
    "        if len(self.n_step_buffer) < self.n_step:\n",
    "            return ()\n",
    "        \n",
    "        # make a n-step transition\n",
    "        rew, next_obs, done = self._get_n_step_info(\n",
    "            self.n_step_buffer, self.gamma\n",
    "        )\n",
    "        obs, act = self.n_step_buffer[0][:2]\n",
    "        \n",
    "        self.obs_buf[self.ptr] = obs\n",
    "        self.next_obs_buf[self.ptr] = next_obs\n",
    "        self.acts_buf[self.ptr] = act\n",
    "        self.rews_buf[self.ptr] = rew\n",
    "        self.done_buf[self.ptr] = done\n",
    "        self.ptr = (self.ptr + 1) % self.max_size\n",
    "        self.size = min(self.size + 1, self.max_size)\n",
    "        \n",
    "        return self.n_step_buffer[0]\n",
    "\n",
    "    def sample_batch(self) -> Dict[str, np.ndarray]:\n",
    "        idxs = np.random.choice(self.size, size=self.batch_size, replace=False)\n",
    "\n",
    "        return dict(\n",
    "            obs=self.obs_buf[idxs],\n",
    "            next_obs=self.next_obs_buf[idxs],\n",
    "            acts=self.acts_buf[idxs],\n",
    "            rews=self.rews_buf[idxs],\n",
    "            done=self.done_buf[idxs],\n",
    "            # for N-step Learning\n",
    "            indices=indices,\n",
    "        )\n",
    "    \n",
    "    def sample_batch_from_idxs(\n",
    "        self, idxs: np.ndarray\n",
    "    ) -> Dict[str, np.ndarray]:\n",
    "        # for N-step Learning\n",
    "        return dict(\n",
    "            obs=self.obs_buf[idxs],\n",
    "            next_obs=self.next_obs_buf[idxs],\n",
    "            acts=self.acts_buf[idxs],\n",
    "            rews=self.rews_buf[idxs],\n",
    "            done=self.done_buf[idxs],\n",
    "        )\n",
    "    \n",
    "    def _get_n_step_info(\n",
    "        self, n_step_buffer: Deque, gamma: float\n",
    "    ) -> Tuple[np.int64, np.ndarray, bool]:\n",
    "        \"\"\"Return n step rew, next_obs, and done.\"\"\"\n",
    "        # info of the last transition\n",
    "        rew, next_obs, done = n_step_buffer[-1][-3:]\n",
    "\n",
    "        for transition in reversed(list(n_step_buffer)[:-1]):\n",
    "            r, n_o, d = transition[-3:]\n",
    "\n",
    "            rew = r + gamma * rew * (1 - d)\n",
    "            next_obs, done = (n_o, d) if d else (next_obs, done)\n",
    "\n",
    "        return rew, next_obs, done\n",
    "\n",
    "    def __len__(self) -> int:\n",
    "        return self.size"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Prioritized replay Buffer\n",
    "\n",
    "`store` method returns boolean in order to inform if a N-step transition has been generated.\n",
    "\n",
    "(Please see *02.per.ipynb* for detailed description about PER.)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "class PrioritizedReplayBuffer(ReplayBuffer):\n",
    "    \"\"\"Prioritized Replay buffer.\n",
    "    \n",
    "    Attributes:\n",
    "        max_priority (float): max priority\n",
    "        tree_ptr (int): next index of tree\n",
    "        alpha (float): alpha parameter for prioritized replay buffer\n",
    "        sum_tree (SumSegmentTree): sum tree for prior\n",
    "        min_tree (MinSegmentTree): min tree for min prior to get max weight\n",
    "        \n",
    "    \"\"\"\n",
    "    \n",
    "    def __init__(\n",
    "        self, \n",
    "        obs_dim: int, \n",
    "        size: int, \n",
    "        batch_size: int = 32, \n",
    "        alpha: float = 0.6,\n",
    "        n_step: int = 1, \n",
    "        gamma: float = 0.99,\n",
    "    ):\n",
    "        \"\"\"Initialization.\"\"\"\n",
    "        assert alpha >= 0\n",
    "        \n",
    "        super(PrioritizedReplayBuffer, self).__init__(\n",
    "            obs_dim, size, batch_size, n_step, gamma\n",
    "        )\n",
    "        self.max_priority, self.tree_ptr = 1.0, 0\n",
    "        self.alpha = alpha\n",
    "        \n",
    "        # capacity must be positive and a power of 2.\n",
    "        tree_capacity = 1\n",
    "        while tree_capacity < self.max_size:\n",
    "            tree_capacity *= 2\n",
    "\n",
    "        self.sum_tree = SumSegmentTree(tree_capacity)\n",
    "        self.min_tree = MinSegmentTree(tree_capacity)\n",
    "        \n",
    "    def store(\n",
    "        self, \n",
    "        obs: np.ndarray, \n",
    "        act: int, \n",
    "        rew: float, \n",
    "        next_obs: np.ndarray, \n",
    "        done: bool,\n",
    "    ) -> Tuple[np.ndarray, np.ndarray, float, np.ndarray, bool]:\n",
    "        \"\"\"Store experience and priority.\"\"\"\n",
    "        transition = super().store(obs, act, rew, next_obs, done)\n",
    "        \n",
    "        if transition:\n",
    "            self.sum_tree[self.tree_ptr] = self.max_priority ** self.alpha\n",
    "            self.min_tree[self.tree_ptr] = self.max_priority ** self.alpha\n",
    "            self.tree_ptr = (self.tree_ptr + 1) % self.max_size\n",
    "        \n",
    "        return transition\n",
    "\n",
    "    def sample_batch(self, beta: float = 0.4) -> Dict[str, np.ndarray]:\n",
    "        \"\"\"Sample a batch of experiences.\"\"\"\n",
    "        assert len(self) >= self.batch_size\n",
    "        assert beta > 0\n",
    "        \n",
    "        indices = self._sample_proportional()\n",
    "        \n",
    "        obs = self.obs_buf[indices]\n",
    "        next_obs = self.next_obs_buf[indices]\n",
    "        acts = self.acts_buf[indices]\n",
    "        rews = self.rews_buf[indices]\n",
    "        done = self.done_buf[indices]\n",
    "        weights = np.array([self._calculate_weight(i, beta) for i in indices])\n",
    "        \n",
    "        return dict(\n",
    "            obs=obs,\n",
    "            next_obs=next_obs,\n",
    "            acts=acts,\n",
    "            rews=rews,\n",
    "            done=done,\n",
    "            weights=weights,\n",
    "            indices=indices,\n",
    "        )\n",
    "        \n",
    "    def update_priorities(self, indices: List[int], priorities: np.ndarray):\n",
    "        \"\"\"Update priorities of sampled transitions.\"\"\"\n",
    "        assert len(indices) == len(priorities)\n",
    "\n",
    "        for idx, priority in zip(indices, priorities):\n",
    "            assert priority > 0\n",
    "            assert 0 <= idx < len(self)\n",
    "\n",
    "            self.sum_tree[idx] = priority ** self.alpha\n",
    "            self.min_tree[idx] = priority ** self.alpha\n",
    "\n",
    "            self.max_priority = max(self.max_priority, priority)\n",
    "            \n",
    "    def _sample_proportional(self) -> List[int]:\n",
    "        \"\"\"Sample indices based on proportions.\"\"\"\n",
    "        indices = []\n",
    "        p_total = self.sum_tree.sum(0, len(self) - 1)\n",
    "        segment = p_total / self.batch_size\n",
    "        \n",
    "        for i in range(self.batch_size):\n",
    "            a = segment * i\n",
    "            b = segment * (i + 1)\n",
    "            upperbound = random.uniform(a, b)\n",
    "            idx = self.sum_tree.retrieve(upperbound)\n",
    "            indices.append(idx)\n",
    "            \n",
    "        return indices\n",
    "    \n",
    "    def _calculate_weight(self, idx: int, beta: float):\n",
    "        \"\"\"Calculate the weight of the experience at idx.\"\"\"\n",
    "        # get max weight\n",
    "        p_min = self.min_tree.min() / self.sum_tree.sum()\n",
    "        max_weight = (p_min * len(self)) ** (-beta)\n",
    "        \n",
    "        # calculate weights\n",
    "        p_sample = self.sum_tree[idx] / self.sum_tree.sum()\n",
    "        weight = (p_sample * len(self)) ** (-beta)\n",
    "        weight = weight / max_weight\n",
    "        \n",
    "        return weight"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Noisy Layer\n",
    "\n",
    "Please see *05.noisy_net.ipynb* for detailed description.\n",
    "\n",
    "**References:**\n",
    "\n",
    "- https://github.com/higgsfield/RL-Adventure/blob/master/5.noisy%20dqn.ipynb\n",
    "- https://github.com/Kaixhin/Rainbow/blob/master/model.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "class NoisyLinear(nn.Module):\n",
    "    \"\"\"Noisy linear module for NoisyNet.\n",
    "    \n",
    "    \n",
    "        \n",
    "    Attributes:\n",
    "        in_features (int): input size of linear module\n",
    "        out_features (int): output size of linear module\n",
    "        std_init (float): initial std value\n",
    "        weight_mu (nn.Parameter): mean value weight parameter\n",
    "        weight_sigma (nn.Parameter): std value weight parameter\n",
    "        bias_mu (nn.Parameter): mean value bias parameter\n",
    "        bias_sigma (nn.Parameter): std value bias parameter\n",
    "        \n",
    "    \"\"\"\n",
    "\n",
    "    def __init__(\n",
    "        self, \n",
    "        in_features: int, \n",
    "        out_features: int, \n",
    "        std_init: float = 0.5,\n",
    "    ):\n",
    "        \"\"\"Initialization.\"\"\"\n",
    "        super(NoisyLinear, self).__init__()\n",
    "        \n",
    "        self.in_features = in_features\n",
    "        self.out_features = out_features\n",
    "        self.std_init = std_init\n",
    "\n",
    "        self.weight_mu = nn.Parameter(torch.Tensor(out_features, in_features))\n",
    "        self.weight_sigma = nn.Parameter(\n",
    "            torch.Tensor(out_features, in_features)\n",
    "        )\n",
    "        self.register_buffer(\n",
    "            \"weight_epsilon\", torch.Tensor(out_features, in_features)\n",
    "        )\n",
    "\n",
    "        self.bias_mu = nn.Parameter(torch.Tensor(out_features))\n",
    "        self.bias_sigma = nn.Parameter(torch.Tensor(out_features))\n",
    "        self.register_buffer(\"bias_epsilon\", torch.Tensor(out_features))\n",
    "\n",
    "        self.reset_parameters()\n",
    "        self.reset_noise()\n",
    "\n",
    "    def reset_parameters(self):\n",
    "        \"\"\"Reset trainable network parameters (factorized gaussian noise).\"\"\"\n",
    "        mu_range = 1 / math.sqrt(self.in_features)\n",
    "        self.weight_mu.data.uniform_(-mu_range, mu_range)\n",
    "        self.weight_sigma.data.fill_(\n",
    "            self.std_init / math.sqrt(self.in_features)\n",
    "        )\n",
    "        self.bias_mu.data.uniform_(-mu_range, mu_range)\n",
    "        self.bias_sigma.data.fill_(\n",
    "            self.std_init / math.sqrt(self.out_features)\n",
    "        )\n",
    "\n",
    "    def reset_noise(self):\n",
    "        \"\"\"Make new noise.\"\"\"\n",
    "        epsilon_in = self.scale_noise(self.in_features)\n",
    "        epsilon_out = self.scale_noise(self.out_features)\n",
    "\n",
    "        # outer product\n",
    "        self.weight_epsilon.copy_(epsilon_out.ger(epsilon_in))\n",
    "        self.bias_epsilon.copy_(epsilon_out)\n",
    "\n",
    "    def forward(self, x: torch.Tensor) -> torch.Tensor:\n",
    "        \"\"\"Forward method implementation.\n",
    "        \n",
    "        We don't use separate statements on train / eval mode.\n",
    "        It doesn't show remarkable difference of performance.\n",
    "        \"\"\"\n",
    "        return F.linear(\n",
    "            x,\n",
    "            self.weight_mu + self.weight_sigma * self.weight_epsilon,\n",
    "            self.bias_mu + self.bias_sigma * self.bias_epsilon,\n",
    "        )\n",
    "    \n",
    "    @staticmethod\n",
    "    def scale_noise(size: int) -> torch.Tensor:\n",
    "        \"\"\"Set scale to make noise (factorized gaussian noise).\"\"\"\n",
    "        x = torch.FloatTensor(np.random.normal(loc=0.0, scale=1.0, size=size))\n",
    "\n",
    "        return x.sign().mul(x.abs().sqrt())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## NoisyNet + DuelingNet + Categorical DQN\n",
    "\n",
    "#### NoisyNet + DuelingNet\n",
    "\n",
    "NoisyLinear is employed for the last two layers of advantage and value layers. The noise should be reset at evey update step.\n",
    "\n",
    "#### DuelingNet + Categorical DQN\n",
    "\n",
    "The dueling network architecture is adapted for use with return distributions. The network has a shared representation, which is then fed into a value stream with atom_size outputs, and into an advantage stream with atom_size × out_dim outputs. For each atom, the value and advantage streams are aggregated, as in dueling DQN, and then passed through a softmax layer to obtain the normalized parametric distributions used to estimate the returns’ distributions.\n",
    "\n",
    "```\n",
    "        advantage = self.advantage_layer(adv_hid).view(-1, self.out_dim, self.atom_size)\n",
    "        value = self.value_layer(val_hid).view(-1, 1, self.atom_size)\n",
    "        q_atoms = value + advantage - advantage.mean(dim=1, keepdim=True)\n",
    "        \n",
    "        dist = F.softmax(q_atoms, dim=-1)\n",
    "```\n",
    "\n",
    "(Please see *04.dueling.ipynb*, *05.noisy_net.ipynb*, *06.categorical_dqn.ipynb* for detailed description of each component's network architecture.)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Network(nn.Module):\n",
    "    def __init__(\n",
    "        self, \n",
    "        in_dim: int, \n",
    "        out_dim: int, \n",
    "        atom_size: int, \n",
    "        support: torch.Tensor\n",
    "    ):\n",
    "        \"\"\"Initialization.\"\"\"\n",
    "        super(Network, self).__init__()\n",
    "        \n",
    "        self.support = support\n",
    "        self.out_dim = out_dim\n",
    "        self.atom_size = atom_size\n",
    "\n",
    "        # set common feature layer\n",
    "        self.feature_layer = nn.Sequential(\n",
    "            nn.Linear(in_dim, 128), \n",
    "            nn.ReLU(),\n",
    "        )\n",
    "        \n",
    "        # set advantage layer\n",
    "        self.advantage_hidden_layer = NoisyLinear(128, 128)\n",
    "        self.advantage_layer = NoisyLinear(128, out_dim * atom_size)\n",
    "\n",
    "        # set value layer\n",
    "        self.value_hidden_layer = NoisyLinear(128, 128)\n",
    "        self.value_layer = NoisyLinear(128, atom_size)\n",
    "\n",
    "    def forward(self, x: torch.Tensor) -> torch.Tensor:\n",
    "        \"\"\"Forward method implementation.\"\"\"\n",
    "        dist = self.dist(x)\n",
    "        q = torch.sum(dist * self.support, dim=2)\n",
    "        \n",
    "        return q\n",
    "    \n",
    "    def dist(self, x: torch.Tensor) -> torch.Tensor:\n",
    "        \"\"\"Get distribution for atoms.\"\"\"\n",
    "        feature = self.feature_layer(x)\n",
    "        adv_hid = F.relu(self.advantage_hidden_layer(feature))\n",
    "        val_hid = F.relu(self.value_hidden_layer(feature))\n",
    "        \n",
    "        advantage = self.advantage_layer(adv_hid).view(\n",
    "            -1, self.out_dim, self.atom_size\n",
    "        )\n",
    "        value = self.value_layer(val_hid).view(-1, 1, self.atom_size)\n",
    "        q_atoms = value + advantage - advantage.mean(dim=1, keepdim=True)\n",
    "        \n",
    "        dist = F.softmax(q_atoms, dim=-1)\n",
    "        dist = dist.clamp(min=1e-3)  # for avoiding nans\n",
    "        \n",
    "        return dist\n",
    "    \n",
    "    def reset_noise(self):\n",
    "        \"\"\"Reset all noisy layers.\"\"\"\n",
    "        self.advantage_hidden_layer.reset_noise()\n",
    "        self.advantage_layer.reset_noise()\n",
    "        self.value_hidden_layer.reset_noise()\n",
    "        self.value_layer.reset_noise()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Rainbow Agent\n",
    "\n",
    "Here is a summary of DQNAgent class.\n",
    "\n",
    "| Method           | Note                                                 |\n",
    "| ---              | ---                                                  |\n",
    "|select_action     | select an action from the input state.               |\n",
    "|step              | take an action and return the response of the env.   |\n",
    "|compute_dqn_loss  | return dqn loss.                                     |\n",
    "|update_model      | update the model by gradient descent.                |\n",
    "|target_hard_update| hard update from the local model to the target model.|\n",
    "|train             | train the agent during num_frames.                   |\n",
    "|test              | test the agent (1 episode).                          |\n",
    "|plot              | plot the training progresses.                        |\n",
    "\n",
    "#### Categorical DQN + Double DQN\n",
    "\n",
    "The idea of Double Q-learning is to reduce overestimations by decomposing the max operation in the target into action selection and action evaluation. Here, we use `self.dqn` instead of `self.dqn_target` to obtain the target actions.\n",
    "\n",
    "```\n",
    "        # Categorical DQN + Double DQN\n",
    "        # target_dqn is used when we don't employ double DQN\n",
    "        next_action = self.dqn(next_state).argmax(1)\n",
    "        next_dist = self.dqn_target.dist(next_state)\n",
    "        next_dist = next_dist[range(self.batch_size), next_action]\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "class DQNAgent:\n",
    "    \"\"\"DQN Agent interacting with environment.\n",
    "    \n",
    "    Attribute:\n",
    "        env (gym.Env): openAI Gym environment\n",
    "        memory (PrioritizedReplayBuffer): replay memory to store transitions\n",
    "        batch_size (int): batch size for sampling\n",
    "        target_update (int): period for target model's hard update\n",
    "        gamma (float): discount factor\n",
    "        dqn (Network): model to train and select actions\n",
    "        dqn_target (Network): target model to update\n",
    "        optimizer (torch.optim): optimizer for training dqn\n",
    "        transition (list): transition information including \n",
    "                           state, action, reward, next_state, done\n",
    "        v_min (float): min value of support\n",
    "        v_max (float): max value of support\n",
    "        atom_size (int): the unit number of support\n",
    "        support (torch.Tensor): support for categorical dqn\n",
    "        use_n_step (bool): whether to use n_step memory\n",
    "        n_step (int): step number to calculate n-step td error\n",
    "        memory_n (ReplayBuffer): n-step replay buffer\n",
    "    \"\"\"\n",
    "\n",
    "    def __init__(\n",
    "        self, \n",
    "        env: gym.Env,\n",
    "        memory_size: int,\n",
    "        batch_size: int,\n",
    "        target_update: int,\n",
    "        gamma: float = 0.99,\n",
    "        # PER parameters\n",
    "        alpha: float = 0.2,\n",
    "        beta: float = 0.6,\n",
    "        prior_eps: float = 1e-6,\n",
    "        # Categorical DQN parameters\n",
    "        v_min: float = 0.0,\n",
    "        v_max: float = 200.0,\n",
    "        atom_size: int = 51,\n",
    "        # N-step Learning\n",
    "        n_step: int = 3,\n",
    "    ):\n",
    "        \"\"\"Initialization.\n",
    "        \n",
    "        Args:\n",
    "            env (gym.Env): openAI Gym environment\n",
    "            memory_size (int): length of memory\n",
    "            batch_size (int): batch size for sampling\n",
    "            target_update (int): period for target model's hard update\n",
    "            lr (float): learning rate\n",
    "            gamma (float): discount factor\n",
    "            alpha (float): determines how much prioritization is used\n",
    "            beta (float): determines how much importance sampling is used\n",
    "            prior_eps (float): guarantees every transition can be sampled\n",
    "            v_min (float): min value of support\n",
    "            v_max (float): max value of support\n",
    "            atom_size (int): the unit number of support\n",
    "            n_step (int): step number to calculate n-step td error\n",
    "        \"\"\"\n",
    "        obs_dim = env.observation_space.shape[0]\n",
    "        action_dim = env.action_space.n\n",
    "        \n",
    "        self.env = env\n",
    "        self.batch_size = batch_size\n",
    "        self.target_update = target_update\n",
    "        self.gamma = gamma\n",
    "        # NoisyNet: All attributes related to epsilon are removed\n",
    "        \n",
    "        # device: cpu / gpu\n",
    "        self.device = torch.device(\n",
    "            \"cuda\" if torch.cuda.is_available() else \"cpu\"\n",
    "        )\n",
    "        print(self.device)\n",
    "        \n",
    "        # PER\n",
    "        # memory for 1-step Learning\n",
    "        self.beta = beta\n",
    "        self.prior_eps = prior_eps\n",
    "        self.memory = PrioritizedReplayBuffer(\n",
    "            obs_dim, memory_size, batch_size, alpha=alpha\n",
    "        )\n",
    "        \n",
    "        # memory for N-step Learning\n",
    "        self.use_n_step = True if n_step > 1 else False\n",
    "        if self.use_n_step:\n",
    "            self.n_step = n_step\n",
    "            self.memory_n = ReplayBuffer(\n",
    "                obs_dim, memory_size, batch_size, n_step=n_step, gamma=gamma\n",
    "            )\n",
    "            \n",
    "        # Categorical DQN parameters\n",
    "        self.v_min = v_min\n",
    "        self.v_max = v_max\n",
    "        self.atom_size = atom_size\n",
    "        self.support = torch.linspace(\n",
    "            self.v_min, self.v_max, self.atom_size\n",
    "        ).to(self.device)\n",
    "\n",
    "        # networks: dqn, dqn_target\n",
    "        self.dqn = Network(\n",
    "            obs_dim, action_dim, self.atom_size, self.support\n",
    "        ).to(self.device)\n",
    "        self.dqn_target = Network(\n",
    "            obs_dim, action_dim, self.atom_size, self.support\n",
    "        ).to(self.device)\n",
    "        self.dqn_target.load_state_dict(self.dqn.state_dict())\n",
    "        self.dqn_target.eval()\n",
    "        \n",
    "        # optimizer\n",
    "        self.optimizer = optim.Adam(self.dqn.parameters())\n",
    "\n",
    "        # transition to store in memory\n",
    "        self.transition = list()\n",
    "        \n",
    "        # mode: train / test\n",
    "        self.is_test = False\n",
    "\n",
    "    def select_action(self, state: np.ndarray) -> np.ndarray:\n",
    "        \"\"\"Select an action from the input state.\"\"\"\n",
    "        # NoisyNet: no epsilon greedy action selection\n",
    "        selected_action = self.dqn(\n",
    "            torch.FloatTensor(state).to(self.device)\n",
    "        ).argmax()\n",
    "        selected_action = selected_action.detach().cpu().numpy()\n",
    "        \n",
    "        if not self.is_test:\n",
    "            self.transition = [state, selected_action]\n",
    "        \n",
    "        return selected_action\n",
    "\n",
    "    def step(self, action: np.ndarray) -> Tuple[np.ndarray, np.float64, bool]:\n",
    "        \"\"\"Take an action and return the response of the env.\"\"\"\n",
    "        next_state, reward, done, _ = self.env.step(action)\n",
    "\n",
    "        if not self.is_test:\n",
    "            self.transition += [reward, next_state, done]\n",
    "            \n",
    "            # N-step transition\n",
    "            if self.use_n_step:\n",
    "                one_step_transition = self.memory_n.store(*self.transition)\n",
    "            # 1-step transition\n",
    "            else:\n",
    "                one_step_transition = self.transition\n",
    "\n",
    "            # add a single step transition\n",
    "            if one_step_transition:\n",
    "                self.memory.store(*one_step_transition)\n",
    "    \n",
    "        return next_state, reward, done\n",
    "\n",
    "    def update_model(self) -> torch.Tensor:\n",
    "        \"\"\"Update the model by gradient descent.\"\"\"\n",
    "        # PER needs beta to calculate weights\n",
    "        samples = self.memory.sample_batch(self.beta)\n",
    "        weights = torch.FloatTensor(\n",
    "            samples[\"weights\"].reshape(-1, 1)\n",
    "        ).to(self.device)\n",
    "        indices = samples[\"indices\"]\n",
    "        \n",
    "        # 1-step Learning loss\n",
    "        elementwise_loss = self._compute_dqn_loss(samples, self.gamma)\n",
    "        \n",
    "        # PER: importance sampling before average\n",
    "        loss = torch.mean(elementwise_loss * weights)\n",
    "        \n",
    "        # N-step Learning loss\n",
    "        # we are gonna combine 1-step loss and n-step loss so as to\n",
    "        # prevent high-variance. The original rainbow employs n-step loss only.\n",
    "        if self.use_n_step:\n",
    "            gamma = self.gamma ** self.n_step\n",
    "            samples = self.memory_n.sample_batch_from_idxs(indices)\n",
    "            elementwise_loss_n_loss = self._compute_dqn_loss(samples, gamma)\n",
    "            elementwise_loss += elementwise_loss_n_loss\n",
    "            \n",
    "            # PER: importance sampling before average\n",
    "            loss = torch.mean(elementwise_loss * weights)\n",
    "\n",
    "        self.optimizer.zero_grad()\n",
    "        loss.backward()\n",
    "        clip_grad_norm_(self.dqn.parameters(), 10.0)\n",
    "        self.optimizer.step()\n",
    "        \n",
    "        # PER: update priorities\n",
    "        loss_for_prior = elementwise_loss.detach().cpu().numpy()\n",
    "        new_priorities = loss_for_prior + self.prior_eps\n",
    "        self.memory.update_priorities(indices, new_priorities)\n",
    "        \n",
    "        # NoisyNet: reset noise\n",
    "        self.dqn.reset_noise()\n",
    "        self.dqn_target.reset_noise()\n",
    "\n",
    "        return loss.item()\n",
    "        \n",
    "    def train(self, num_frames: int, plotting_interval: int = 200):\n",
    "        \"\"\"Train the agent.\"\"\"\n",
    "        self.is_test = False\n",
    "        \n",
    "        state = self.env.reset()\n",
    "        update_cnt = 0\n",
    "        losses = []\n",
    "        scores = []\n",
    "        score = 0\n",
    "\n",
    "        for frame_idx in range(1, num_frames + 1):\n",
    "            action = self.select_action(state)\n",
    "            next_state, reward, done = self.step(action)\n",
    "\n",
    "            state = next_state\n",
    "            score += reward\n",
    "            \n",
    "            # NoisyNet: removed decrease of epsilon\n",
    "            \n",
    "            # PER: increase beta\n",
    "            fraction = min(frame_idx / num_frames, 1.0)\n",
    "            self.beta = self.beta + fraction * (1.0 - self.beta)\n",
    "\n",
    "            # if episode ends\n",
    "            if done:\n",
    "                state = self.env.reset()\n",
    "                scores.append(score)\n",
    "                score = 0\n",
    "\n",
    "            # if training is ready\n",
    "            if len(self.memory) >= self.batch_size:\n",
    "                loss = self.update_model()\n",
    "                losses.append(loss)\n",
    "                update_cnt += 1\n",
    "                \n",
    "                # if hard update is needed\n",
    "                if update_cnt % self.target_update == 0:\n",
    "                    self._target_hard_update()\n",
    "\n",
    "            # plotting\n",
    "            if frame_idx % plotting_interval == 0:\n",
    "                self._plot(frame_idx, scores, losses)\n",
    "                \n",
    "        self.env.close()\n",
    "                \n",
    "    def test(self) -> None:\n",
    "        \"\"\"Test the agent.\"\"\"\n",
    "        self.is_test = True\n",
    "        \n",
    "        state = self.env.reset()\n",
    "        done = False\n",
    "        score = 0\n",
    "        \n",
    "        while not done:\n",
    "            self.env.render()\n",
    "            action = self.select_action(state)\n",
    "            next_state, reward, done = self.step(action)\n",
    "\n",
    "            state = next_state\n",
    "            score += reward\n",
    "        \n",
    "        print(\"score: \", score)\n",
    "        self.env.close()\n",
    "\n",
    "    def _compute_dqn_loss(self, samples: Dict[str, np.ndarray], gamma: float) -> torch.Tensor:\n",
    "        \"\"\"Return categorical dqn loss.\"\"\"\n",
    "        device = self.device  # for shortening the following lines\n",
    "        state = torch.FloatTensor(samples[\"obs\"]).to(device)\n",
    "        next_state = torch.FloatTensor(samples[\"next_obs\"]).to(device)\n",
    "        action = torch.LongTensor(samples[\"acts\"]).to(device)\n",
    "        reward = torch.FloatTensor(samples[\"rews\"].reshape(-1, 1)).to(device)\n",
    "        done = torch.FloatTensor(samples[\"done\"].reshape(-1, 1)).to(device)\n",
    "        \n",
    "        # Categorical DQN algorithm\n",
    "        delta_z = float(self.v_max - self.v_min) / (self.atom_size - 1)\n",
    "\n",
    "        with torch.no_grad():\n",
    "            # Double DQN\n",
    "            next_action = self.dqn(next_state).argmax(1)\n",
    "            next_dist = self.dqn_target.dist(next_state)\n",
    "            next_dist = next_dist[range(self.batch_size), next_action]\n",
    "\n",
    "            t_z = reward + (1 - done) * gamma * self.support\n",
    "            t_z = t_z.clamp(min=self.v_min, max=self.v_max)\n",
    "            b = (t_z - self.v_min) / delta_z\n",
    "            l = b.floor().long()\n",
    "            u = b.ceil().long()\n",
    "\n",
    "            offset = (\n",
    "                torch.linspace(\n",
    "                    0, (self.batch_size - 1) * self.atom_size, self.batch_size\n",
    "                ).long()\n",
    "                .unsqueeze(1)\n",
    "                .expand(self.batch_size, self.atom_size)\n",
    "                .to(self.device)\n",
    "            )\n",
    "\n",
    "            proj_dist = torch.zeros(next_dist.size(), device=self.device)\n",
    "            proj_dist.view(-1).index_add_(\n",
    "                0, (l + offset).view(-1), (next_dist * (u.float() - b)).view(-1)\n",
    "            )\n",
    "            proj_dist.view(-1).index_add_(\n",
    "                0, (u + offset).view(-1), (next_dist * (b - l.float())).view(-1)\n",
    "            )\n",
    "\n",
    "        dist = self.dqn.dist(state)\n",
    "        log_p = torch.log(dist[range(self.batch_size), action])\n",
    "        elementwise_loss = -(proj_dist * log_p).sum(1)\n",
    "\n",
    "        return elementwise_loss\n",
    "\n",
    "    def _target_hard_update(self):\n",
    "        \"\"\"Hard update: target <- local.\"\"\"\n",
    "        self.dqn_target.load_state_dict(self.dqn.state_dict())\n",
    "                \n",
    "    def _plot(\n",
    "        self, \n",
    "        frame_idx: int, \n",
    "        scores: List[float], \n",
    "        losses: List[float],\n",
    "    ):\n",
    "        \"\"\"Plot the training progresses.\"\"\"\n",
    "        clear_output(True)\n",
    "        plt.figure(figsize=(20, 5))\n",
    "        plt.subplot(131)\n",
    "        plt.title('frame %s. score: %s' % (frame_idx, np.mean(scores[-10:])))\n",
    "        plt.plot(scores)\n",
    "        plt.subplot(132)\n",
    "        plt.title('loss')\n",
    "        plt.plot(losses)\n",
    "        plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Environment\n",
    "\n",
    "You can see the [code](https://github.com/openai/gym/blob/master/gym/envs/classic_control/cartpole.py) and [configurations](https://github.com/openai/gym/blob/master/gym/envs/__init__.py#L53) of CartPole-v0 from OpenAI's repository."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "# environment\n",
    "env_id = \"CartPole-v0\"\n",
    "env = gym.make(env_id)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Set random seed"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[777]"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "seed = 777\n",
    "\n",
    "def seed_torch(seed):\n",
    "    torch.manual_seed(seed)\n",
    "    if torch.backends.cudnn.enabled:\n",
    "        torch.backends.cudnn.benchmark = False\n",
    "        torch.backends.cudnn.deterministic = True\n",
    "\n",
    "np.random.seed(seed)\n",
    "random.seed(seed)\n",
    "seed_torch(seed)\n",
    "env.seed(seed)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Initialize"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "cuda\n"
     ]
    }
   ],
   "source": [
    "# parameters\n",
    "num_frames = 20000\n",
    "memory_size = 1000\n",
    "batch_size = 32\n",
    "target_update = 100\n",
    "\n",
    "# train\n",
    "agent = DQNAgent(env, memory_size, batch_size, target_update)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Train"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 1440x360 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "agent.train(num_frames)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Test\n",
    "\n",
    "Run the trained agent (1 episode)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "score:  200.0\n"
     ]
    }
   ],
   "source": [
    "agent.env = gym.wrappers.Monitor(env, \"videos\", force=True)\n",
    "agent.test()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Render"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "videos/openaigym.video.0.14596.video000000.mp4\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "\n",
       "        <video alt=\"test\" controls>\n",
       "        <source src=\"data:video/mp4;base64,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AgAAAAABAAAAAAAAAAEAAAEAAAAAAQAABQAAAAABAAACAAAAAAEAAAAAAAAAAQAAAQAAAAABAAAFAAAAAAEAAAIAAAAAAQAAAAAAAAABAAABAAAAAAEAAAMAAAAAAQAAAQAAAAABAAAFAAAAAAEAAAIAAAAAAQAAAAAAAAABAAABAAAAAAEAAAUAAAAAAQAAAgAAAAABAAAAAAAAAAEAAAEAAAAAAQAABQAAAAABAAACAAAAAAEAAAAAAAAAAQAAAQAAAAABAAAFAAAAAAEAAAIAAAAAAQAAAAAAAAABAAABAAAAAAEAAAUAAAAAAQAAAgAAAAABAAAAAAAAAAEAAAEAAAAAAQAABQAAAAABAAACAAAAAAEAAAAAAAAAAQAAAQAAAAABAAAFAAAAAAEAAAIAAAAAAQAAAAAAAAABAAABAAAAAAEAAAUAAAAAAQAAAgAAAAABAAAAAAAAAAEAAAEAAAAAAQAABQAAAAABAAACAAAAAAEAAAAAAAAAAQAAAQAAAAABAAAFAAAAAAEAAAIAAAAAAQAAAAAAAAABAAABAAAAAAEAAAUAAAAAAQAAAgAAAAABAAAAAAAAAAEAAAEAAAAAAQAABQAAAAABAAACAAAAAAEAAAAAAAAAAQAAAQAAAAABAAAFAAAAAAEAAAIAAAAAAQAAAAAAAAABAAABAAAAAAEAAAUAAAAAAQAAAgAAAAABAAAAAAAAAAEAAAEAAAAAAQAABQAAAAABAAACAAAAAAEAAAAAAAAAAQAAAQAAAAABAAAFAAAAAAEAAAIAAAAAAQAAAAAAAAABAAABAAAAAAEAAAUAAAAAAQAAAgAAAAABAAAAAAAAAAEAAAEAAAAAAQAABQAAAAABAAACAAAAAAEAAAAAAAAAAQAAAQAAAAABAAAFAAAAAAEAAAIAAAAAAQAAAAAAAAABAAABAAAAAAEAAAUAAAAAAQAAAgAAAAABAAAAAAAAAAEAAAEAAAAAAQAABQAAAAABAAACAAAAAAEAAAAAAAAAAQAAAQAAAAABAAAFAAAAAAEAAAIAAAAAAQAAAAAAAAABAAABAAAAAAEAAAUAAAAAAQAAAgAAAAABAAAAAAAAAAEAAAEAAAAAAQAABQAAAAABAAACAAAAAAEAAAAAAAAAAQAAAQAAAAABAAAFAAAAAAEAAAIAAAAAAQAAAAAAAAABAAABAAAAAAEAAAUAAAAAAQAAAgAAAAABAAAAAAAAAAEAAAEAAAAAAQAABQAAAAABAAACAAAAAAEAAAAAAAAAAQAAAQAAAAABAAAFAAAAAAEAAAIAAAAAAQAAAAAAAAABAAABAAAAAAEAAAUAAAAAAQAAAgAAAAABAAAAAAAAAAEAAAEAAAAAAQAABQAAAAABAAACAAAAAAEAAAAAAAAAAQAAAQAAAAABAAAFAAAAAAEAAAIAAAAAAQAAAAAAAAABAAABAAAAAAEAAAUAAAAAAQAAAgAAAAABAAAAAAAAAAEAAAEAAAAAAQAABQAAAAABAAACAAAAAAEAAAAAAAAAAQAAAQAAAAABAAAFAAAAAAEAAAIAAAAAAQAAAAAAAAABAAABAAAAAAEAAAUAAAAAAQAAAgAAAAABAAAAAAAAAAEAAAEAAAAAAQAABQAAAAABAAACAAAAAAEAAAAAAAAAAQAAAQAAAAABAAADAAAAAAEAAAEAAAAAHHN0c2MAAAAAAAAAAQAAAAEAAADJAAAAAQAAAzhzdHN6AAAAAAAAAAAAAADJAAAEoAAAANUAAABdAAAAOwAAAEwAAADmAAAAPQAAADoAAAA4AAAAWQAAADUAAABVAAAAPQAAACoAAAAoAAAAbwAAADoAAAAuAAAAJwAAAIkAAAA/AAAAQAAAADwAAABPAAAAYQAAAD0AAAA7AAAAXQAAAEsAAABEAAAAUgAAAIsAAABIAAAAPgAAADEAAABhAAAAPgAAAGEAAAA+AAAANAAAAEcAAABiAAAAXgAAADIAAAA8AAAAWgAAADkAAAA3AAAAMAAAAHIAAAA7AAAAJgAAACQAAABXAAAANwAAAGMAAAA9AAAAOQAAADkAAABJAAAASwAAAEMAAAA6AAAAbAAAAD4AAAA1AAAAOwAAAGUAAABOAAAARAAAADYAAABwAAAAaQAAAC8AAABYAAAAfgAAAGkAAAA/AAAARQAAAGIAAABDAAAAQQAAADgAAABTAAAARwAAAFgAAAArAAAAdwAAAFoAAABQAAAARAAAAHYAAABXAAAAKgAAAD8AAABrAAAAagAAACoAAAAoAAAAZQAAADUAAAApAAAALwAAAFAAAABHAAAAIwAAACEAAABzAAAAQAAAACUAAAAyAAAAUwAAAFIAAAAnAAAAIQAAADsAAAA1AAAAIAAAACgAAABGAAAALQAAACMAAAAoAAAAIwAAADIAAAAfAAAAIAAAADwAAAA0AAAAHwAAACEAAABIAAAAMwAAACMAAAAfAAAASAAAACYAAAAtAAAAHwAAADAAAAAlAAAAKwAAABYAAAAzAAAALgAAABkAAAAbAAAAMQAAAD8AAAAtAAAALAAAAFgAAAAlAAAAHgAAABgAAAAgAAAAJgAAACgAAAAlAAAAUAAAACsAAAAZAAAANAAAACUAAAA8AAAAMwAAACcAAAAkAAAAHgAAABkAAAAYAAAATAAAADQAAAAWAAAAIAAAAHEAAAA3AAAAGwAAACMAAABAAAAANAAAACgAAAAfAAAAPgAAACQAAAAfAAAAGgAAACcAAAA6AAAAHAAAACEAAAA4AAAANQAAACEAAAAiAAAAQgAAADQAAAAqAAAAHgAAACkAAAAfAAAAFHN0Y28AAAAAAAAAAQAAADAAAABidWR0YQAAAFptZXRhAAAAAAAAACFoZGxyAAAAAAAAAABtZGlyYXBwbAAAAAAAAAAAAAAAAC1pbHN0AAAAJal0b28AAAAdZGF0YQAAAAEAAAAATGF2ZjU3LjgzLjEwMA==\" type=\"video/mp4\" />\n",
       "        </video>\n",
       "        "
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import base64\n",
    "import glob\n",
    "import io\n",
    "import os\n",
    "\n",
    "from IPython.display import HTML, display\n",
    "\n",
    "\n",
    "def ipython_show_video(path: str) -> None:\n",
    "    \"\"\"Show a video at `path` within IPython Notebook.\"\"\"\n",
    "    if not os.path.isfile(path):\n",
    "        raise NameError(\"Cannot access: {}\".format(path))\n",
    "\n",
    "    video = io.open(path, \"r+b\").read()\n",
    "    encoded = base64.b64encode(video)\n",
    "\n",
    "    display(HTML(\n",
    "        data=\"\"\"\n",
    "        <video alt=\"test\" controls>\n",
    "        <source src=\"data:video/mp4;base64,{0}\" type=\"video/mp4\" />\n",
    "        </video>\n",
    "        \"\"\".format(encoded.decode(\"ascii\"))\n",
    "    ))\n",
    "\n",
    "list_of_files = glob.glob(\"videos/*.mp4\")\n",
    "latest_file = max(list_of_files, key=os.path.getctime)\n",
    "print(latest_file)\n",
    "ipython_show_video(latest_file)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
